1. Introduction > 2. Mon premier projet > 3. Modèle > 4. Vue > 5. Liste de données > 6. Mapping objet/relationnel > 7. Contrôleurs > 8. Application > 9. Personnalisation

Chapitre 3

La couche modèle dans une application orientée objet contient la logique métier, c'est-à-dire la structure des données et tous les calculs, les validations et processus associés aux données.
OpenXava est un framework (cadre de travail) orienté modèle, dans lequel celui-ci est le plus important et tout le reste (entre autre l'interface utilisateur) en dépend. La façon de définir un modèle dans OpenXava est d'utiliser des classes Java pures (même si une version XML est aussi disponible). OpenXava genère une application complète à partir de la définition de votre modèle.

Composant métier

L'unité de base pour créer une application OpenXava est le composant métier. Les composants métier sont définis par une classe Java appelée Entity. Cette classe une entité classique EJB3, en d'autres mots, une classe POJO (Plain Old Java Object) agrémentée d'annotations qui suivent le standard JPA (Java Persistence API).
JPA est le standard Java pour gérer la persistance, soit pour que les objets puissent sauvegarder leur état dans une base de données. Si vous avez déjà développé des applications avec JPA, vous savez déjà développer des applications OpenXava.
En utilisant une simple classe Java, vous pouvez définir un composant métier avec :
  • le modèle : Structure des données, validateurs, calculs
  • la vue : La présentation du modèle à l'utilisateur final
  • la liste : la présentation des données du modèle en mode liste (en mode tableau)
  • le mapping objet-relationnel : La méthode de stockage et de récupération de l'état de l'objet dans la base de donnée
Ce chapitre explique comme définir la partie modèle, c'est-à-dire tout ce qui concerne la structure, les validateurs, les calculs, etc.

Entity

Pour définir la partie modèle, vous devez déclarer une classe Java avec des annotations. En plus de ses propres annotations, OpenXava utilise les annotations JPA, validateur Hibernate et le Hibernate Annotations. Cette classe java est une entité, soit une classe persistante qui représente un concept métier.
Dans ce chapitre, JPA est utilisé pour indiquer que l'annotation provient du standard de persistance Java, HV pour indiquer qu'elle représente une annotation du validateur Hibernate, HA pour indiquer qu'elle représente une annotation du Hibernate Annotations et OX pour les annotations OpenXava.
Voici la syntaxe pour une entité :
@Entity // 1
@EntityValidator // 2
@RemoveValidator // 3
public class EntityName { // 4
 // Propriétés // 5
 // Références // 6
 // Collections // 7
 // Méthodes // 8
 // Requêtes // 9
 // Méthodes de rappel // 10
}
  1. @Entity (JPA, un, requis) : Indique que cette classe est une entité JPA, c'est-à-dire que les instances de cette classe seront persistées.
  2. @EntityValidator (OX, plusieurs, optionnel) : Exécute une validation au niveau du modèle. Le validateur peut recevoir les valeurs de plusieurs propriétés du modèle. Pour valider une propriété unique, il est préféreable d'utiliser un validateur au niveau de la propriété.
  3. @RemoveValidator (OX, plusieurs, optionnel) : S'exécute avant la suppression d'une instance et peut même l'empêcher.
  4. Déclaration de classe : Comme toute classe Java. Vous pouvez utiliser l'héritage (extends) et l'implémentation d'interfaces (implements).
  5. Propriétés : les attributs classiques d'une classe Java. Ils représentent l'état principal de l'objet.
  6. Références : les références à d'autres entités ou classes embarquées.
  7. Collections : collections de références à d'autres entités ou classes embarquées.
  8. Méthodes : méthode Java avec la logique métier
  9. Requêtes : les méthodes de requêtes sont des méthodes statiques qui recherchent dans la base de données en utilisant les facilités de JPA.
  10. Méthodes de rappel : Méthodes de rappel contenant la logique métier qui doit être exécuté lors de la création, du chargement, de la modification, de la suppression, etc. de l'instance.

Classes embarquées

La spécification JPA précise :
"Une entité peut utiliser d'autres classes à granularité plus fine pour représenter l'état d'une entité. Les instances de ces classes, contrairement aux instances des entités elles-mêmes, ne possèdent pas une identité persistante. Elles existent plutôt comme objets embarqués dans l'entité à laquelle elles appartiennent. De tels objets appartiennent strictement à l'entité propriétaire et ne peuvent être partagées entre plusieurs entités persistées.
La syntaxe d'une classe embarquée est la suivante :
@Embeddable // 1
public class EmbeddableName { // 2
 // Propriétés // 3
 // Références // 4
 // Méthodes // 5
}
 
  1. @Embeddable (JPA, unique, requis). Indique que cette classe est une classe embarquée JPA, en d'autres mots, ses instances feront partie d'objets persistants.
  2. Déclaration de classe. Idem que pour une classe Java traditionnelle. Vous pouvez utiliser extends et implements.
  3. Propriétés. Les propriétés traditionnelles de Java
  4. Références. Les références à d'autres entités ou classes embarquées.
  5. Méthodes. Méthodes Java contenant la logique métier.

Propriétés

Une propriété représente un état d'un objet qui peut être lu et parfois mis à jour. L'objet n'a pas l'obligation formelle d'enregistrer physiquement la donnée de la propriété. Il suffit qu'il retourne une valeur lorsque nécessaire.
La syntaxe pour définir une propriété est la suivante :
@Stereotype // 1
@Column(length=) @Column(precision=) @Max @Length(max=) @Digits(integer=) @Digits(integerDigits=) // 2
@Digits(integer=) @Digits(fraction=) // 3
@Required @Min @Range(min=) @Length(min=) // 4
@Id // 5
@Hidden // 6
@SearchKey // 7
@Version // 8
@Formula // 9 Depuis v3.1.4
@DefaultValueCalculator // 10
@PropertyValidator // 11
private type propertyName; // 12
public type getPropertyName() { ... } // 12
public void setPropertyName(type newValue) { ... } // 12
  1. @Stereotype (OX, optionnel). Spécifie un comportement particulier pour certaines propriétés
  2. @Column(length=) (JPA), @Max (BV, HV), @Length(max=) (HV), @Digits(integer=) (BV), @Digits(integerDigits=) (HV). (optionnel, en général. vous n'utilisez que l'une de ces annotations). Longueur de la propriété en terme de caractères, sauf @Max qui spécifie la longueur maximum. Elles sont très utiles pour la génération des interfaces graphiques. Si vous ne spécifiez pas de longueur, une valeur par défaut est sélectionnée. Celle-ci est associée au type ou stéréotype et récupérée dans le fichier default-size.xml. @Max and @Digits can be from Bean Validation (new in v4.1) or Hibernate Validator (please, translate to French).
  3. @Column(scale=) (JPA), @Digits(fraction=) (BV), @Digits(fractionalDigits=) (HV, optionnel). Échelle de la propriété (taille de la partie décimale). Ne s'applique qu'aux propriétés numériques. If you do not use @Column or @Digits a default value is assumed, if you use @Column without scale a default value is assumed, however if you use @Digits without fraction or fractionalDigits, 0 is assumed (please, translate to French). La valeur par défaut est associée au type ou stéréotype et récupérée dans le fichier default-size.xml. @Digits can be from Bean Validation (new in v4.1) or Hibernate Validator (please, translate to French).
  4. @Required (OX), @Min (HV), @Range(min=) (HV), @Length(min=) (HV) (optionnel, en général. vous n'utilisez que l'une de ces annotations). Indique si la propriété est requise. Dans le cas de @Min, @Range et @Length, vous devez spécifier une valeur plus grande que zéro pour min afin que la propriété soit requise. Par défaut, une propriété et requise pour les clés cachées (depuis v2.1.3) et pas dans les autres cas. Lors de l'enregistrement d'un objet, OpenXava vérifie si les propriétés requises sont présentes. Si ce n'est pas le cas, l'enregistrement n'est pas effectué et une liste d'erreurs de validation est retournée. La logique pour déterminer si une propriété est présente ou non peut être configurée en créant un fichier nommé validators.xml dans votre projet. La syntaxe est accessible dans le fichier OpenXava/xava/default-validators.xml. @Required est une contrainte d'Hibernate Validator (depuis v3.0.1)
  5. @Id (JPA, optionnel). Indique la propriété fait partie de la clé. Au moins une propriété (ou référence) doit être une clé. La combinaison de propriétés clés (et de référence clés) doivent être liées à un groupe de colonnes de la base de données qui n'ont pas de données dupliquées, typiquement les clés primaires.
  6. @Hidden (OX, optionnel). Une propriété cachée comporte un sens pour le développeur, mais pas pour l'utilisateur. Les propriétés cachées sont exclues lors de la génération automatiquement de l'interface graphique. Toutefois, au niveau du code Java, ellse sont présentes et complètement fonctionnelles. Même si vous les spécifiez explicitement dans une vue, elle ne sera pas affichée dans l'interface graphique.
  7. @SearchKey (OX, optionnel). Les propriétés clés de recherche sont utilisées par l'utilisateur comme clés pour rechercher des objets. Elles sont éditable dans l'interface graphique de références permettant à l'utilisateur d'entrer le critère de recherche. OpenXava utilise les propriétés @Id par défaut pour rechercher un objet, mais si celles-ci sont cachées, il utilise la première propriété de la vue. Avec @SearchKey, vous pouvez spécifer explicitement les propriétés pour la recherche.
  8. @Version (JPA, optionnel). Une propriété de version est utilisée pour le contrôle optimiste de concurrence. Si vous souhaitez contrôler la concurrence, vous n'avez besoin que d'annoter une seule propriété avec @Version dans votre entité. Une seule propriété version ne devrait être utilisée par entité. Les types suivants sont valables pour les propriétés de version : int, Integer, short, Short, long, Long, Timestamp. Les propriétés de version sont considérées comme cachées.
  9. @Formula (HA, optionnel): Calcul de la valeur d'une propriété en utilisant la base de données. Obligatoirement un fragment SQL valide.
  10. @DefaultValueCalculator (OX, un seul, optionnel). Implémente la logique pour le calcul de la valeur par défaut (initiale) pour cette propriété. Une propriété annotée avec @DefaultValueCalculator possède un setter et est persistante.
  11. @PropertyValidator (OX, plusieurs, optionnel). Implémente la logique de validation à exécuter sur cette propriété avant de modifier ou créer l'objet qui la contient.
  12. Déclaration de propriété. Une déclaration Java type de propriété avec ces getters et setters. Vous pouvez créer une propriété calculée en spécifiant uniquement un getter sans champs ni setter. Tout type disponible pour JPA est possible, vous n'avez qu'à déterminer le type Hibernate afin de permettre la sauvegarde dans la base de données et une éditeur OpenXava pour le rendu HTML.

Stéréotype

Un stéréotype (@Stereotype) représente une méthode de déterminer le comportement spécifique d'un type. Par exemple, un nom, un commentaire, une description, etc. correspondent tous au type Java java.lang.String, mais vous voudriez sûrement bénéficier de validateurs, de longueurs par défaut, d'éditeurs visuels, etc. différents dans chacun de ces cas et ainsi les définir plus finement. Vous pouvez y arriver en assignant à chacune de ces propriétés un stéréotype. Cela signifie que vous pourriez avoir à disposition les stéréotype NOM, COMMENTAIRE, DESCRIPTION et les associer à vos propriétés.
OpenXava est livré avec les stéréotypes suivants :
  • DINERO, MONEY (stéréotype monétaire)
  • FOTO, PHOTO, IMAGEN, IMAGE (stéréotype graphique)
  • TEXTO_GRANDE, MEMO, TEXT_AREA (stéréotype de zone de texte)
  • ETIQUETA, LABEL (stéréotype de libellé)
  • ETIQUETA_NEGRITA, BOLD_LABEL (stéréotype de libellé gras)
  • HORA, TIME (stéréotype de temps)
  • FECHAHORA, DATETIME (stéréotype de date et temps)
  • GALERIA_IMAGENES, IMAGES_GALLERY (stéréotype de galerie d'images) (instructions de mise oeuvre)
  • RELLENADO_CON_CEROS, ZEROS_FILLED (stéréotype de nombre avec remplissage de zéros)
  • TEXTO_HTML, HTML_TEXT (stéréotype de texte avec format éditable)
  • IMAGE_LABEL, ETIQUETA_IMAGEN (stéréotype d'image dépendante du contenu de la propriété)
  • EMAIL (stéréotype d'adresse de messagerie)
  • TELEFONO, TELEPHONE (stéréotype de numéro de téléphone)
  • WEBURL (stéréotype d'adresse Internet - URL)
  • IP (stéréotype d'adresse IP)
  • ISBN (stéréotype de numéro ISBN pour les livres)
  • TARJETA_CREDITO, CREDIT_CARD (stéréotype pour les numéros de cartes de crédit)
  • LISTA_EMAIL, EMAIL_LIST (stéréotype pour les listes d'adresses de messagerie)
  • DOCUMENT_LIBRARY, LIBRERIA_DOCUMENTOS (depuis v4m6, please translate to French: it only works inside Liferay)
  • PASSWORD, CONTRASENA (depuis v4.1)
  • MAC (depuis v4.8)
  • FILE, ARCHIVO (depuis v5.0) (instructions de mise oeuvre)
A présent, regardons comment définir un stéréotype. Nous allons créer un stéréotype PERSONNE_NOM qui représente le nom de personnes. Ouvrez (ou créez) le fichier editors.xml dans le dossier xava de votre projet et ajoutez l'entrée suivante :
<editor url="personNameEditor.jsp">
 <for-stereotype stereotype="PERSONNE_NOM"/>
</editor>
Ainsi, vous définissez l'éditeur à afficher lors de l'édition de propriétés qui répondent au stéréotype PERSONNE_NOM. Ensuite, il est utile d'indiquer la taille par défaut . Vous y parvenez en ajoutant l'entrée suivante dans le fichier default-size.xml de votre projet:
<for-stereotype name="PERSONNE_NOM" size="40"/>
Ainsi, si vous ne spécifiez pas une longueur pour votre propriété du type PERSONNE_NOM, sa taille par défaut sera de 40 unités. Il est plus rare de définir un validateur. Si toutefois vous en avez besoin, vous pouvez ajouter cette entrée dans le fichier validators.xml de votre projet :
<required-validator>
 <validator-class class="org.openxava.validators.NotBlankCharacterValidator"/>
 <for-stereotype stereotype="PERSONNE_NOM"/>
</required-validator>
Ce validateur s'assure par exemple que la valeur du nom ne contient pas de caractères "blancs" (espace, tabulation, ...).
A présent, tout est prêt pour définir des propriétés de type PERSONNE_NOM :
@Stereotype("PERSONNE_NOM")
private String nom;
Dans ce cas, une taille de 40 caractères et un type java.lang.String sont assumés. En outre, le validateur NotBlankCharacterValidator sera exécuté à la vérification si la propriété est requise.

Stéréotype IMAGE_GALLERY

Si vous souhaitez qu'une propriété de votre composant contienne une galerie d'images, vous avez uniquement besoin de déclarer votre propriété avec les stéréotype IMAGE_GALLERY, ainsi :
@Stereotype("IMAGES_GALLERY")
private String photos;
Ensuite, dans le mapping objet-relationnel, vous devez spécifier une colonne de table capable de stocker une chaîne de caractère (java.lang.String) d'une taille de 32 unités (VARCHAR(32)). Et voilà tout pour votre projet.
Pour utiliser ce stéréotype, vous devez configurer votre système de manière appropriée. Tout d'abord, créez une table dans votre base de données pour stocker les images :
CREATE TABLE IMAGES (
 ID VARCHAR(32) NOT NULL PRIMARY KEY,
 GALLERY VARCHAR(32) NOT NULL,
 IMAGE BLOB);
CREATE INDEX IMAGES01
 ON IMAGES (GALLERY);
Le type de la colonne IMAGE peut être un autre plus adapté à votre base de donnée pour stocker des tableaux de byte (byte[]), par example LONGVARBINARY. Et finalement, vous devez définir la relation dans le fichier persistence/hibernate.cfg.xml comme ceci:
<hibernate-configuration>
 <session-factory>
 ...
 <mapping resource="GalleryImage.hbm.xml"/>
 ...
 </session-factory>
</hibernate-configuration>
Après ces étapes, vous pouvez utiliser le stéréotype IMAGES_GALLERY dans tous les composants de votre application.

FILE stereotype (new in v5.0)

Please, translate this section to French
If you want a property of your component to attach a file, you only have to declare your property with the FILE stereotype, in this way:
@Stereotype("FILE")
@Column(length=32)
private String document;
OpenXava can be configured to store the files in the database or the file system.

Storage in File System

It is the default and requires no additional configuration.
The storage directory is $HOME/oxfiles (GNU/Linux) or %USERPROFILE%\oxfiles (Windows). We can set a different directory using the filesPath property in xava.properties.

Storage in Database

You need three additional settings:
  • Set the filePersistorClass property to xava.properties
filePersistorClass=org.openxava.web.editors.JPAFilePersistor
...
  • Modify the persistence.xml
<persistence-unit name="default">
 <provider>org.hibernate.ejb.HibernatePersistence</provider>
 <non-jta-data-source>java:comp/env/jdbc/OpenXavaTestDS</non-jta-data-source>
 <class>org.openxava.session.GalleryImage</class>
 <class>org.openxava.web.editors.AttachedFile</class>
  ...
</persistence-unit>
 
        ...
 
<persistence-unit name="junit">
 <provider>org.hibernate.ejb.HibernatePersistence</provider>
 <class>org.openxava.web.editors.AttachedFile</class>
 ....
</persistence-unit>
Note that we added <class>org.openxava.web.editors.AttachedFile</class> both persistence units.
  • When the database is generated, the table OXFILES is created:
CREATE TABLE OXFILES (
 ID VARCHAR(32) NOT NULL PRIMARY KEY,
 NAME VARCHAR(255),
 DATA LONGVARBINARY,
 LIBRARYID VARCHAR(32)
);
We check that the type of DATA column is the most suitable type for storing byte[] (in our case LONGVARBINARY).

Concurrence et version des propriétés

La concurrence est la capacité du système a permettre à plusieurs utilisateurs d'enregistrer des données simultanément sans perte. OpenXava utilise la concurrence optimiste de JPA. En utilisant ce type de concurrence, les enregistrements ne sont pas bloqués tout en permettant une haute concurrence sans impacter l'intégrité des données.
Par exemple, si un utilisateur A consulte un enregistrement et que l'utilisateur B ouvre celui-ci, le modifie et l'enregistre entre-temps, lorsque l'utilisateur A essaie d'enregistrer les données, il recevra une erreur lui demandant de rafraîchir les informations avant d'essayer d'enregistrer à nouveau avec ses modifications. Pour activer la gestion de la concurrence pour votre entité, il suffit de déclarer une propriété avec l'annotation @Version, comme ceci :
@Version
private Integer version;
Cette propriété est utilisée par le moteur de persistance et ne devrait pas être manipulée directement par votre application ou par les utilisateurs.

Enums

OpenXava permet d'utiliser les enums tels que définis dans Java 5. Un enum vous permet de spécifier une propriété qui n'accepte que certains valeurs prédéfinies. C'est facile à utiliser comme l'exemple ci-après :
private Distance distance;
public enum Distance { LOCAL, NATIONAL, INTERNATIONAL };
La propriété distance n'accepte que les valeurs suivantes : LOCAL, NATIONAL, INTERNATIONAL et comme l'annotation @Required n'est pas présente, pas de valeur (null) est aussi possible.
Au niveau de l'interface utilisateur, l'implémentation actuelle utilise une liste déroulante de choix. Le libellé de chaque valeur est obtenu dans les fichiers i18n. Au niveau de la base de donnée, la valeur est enregistrée par défaut comme un entier (dans notre cas, 0 pour LOCAL, 1 pour NATIONAL, 2 pour INTERNATIONAL et null pour pas de valeur). Il est toutefois simple de définir un autre type et travailler avec des base de données légataires. Plus d'informations peuvent être obtenues dans le chapitre consacré au mapping objet relationnel.

Propriétés calculées

Les propriétés calculées sont toujours en lecture seule (elle n'ont qu'un getter) et ne sont pas persistantes (elle ne correspondent à aucune colonne de table de base de données). Une propriété calculée est définie comme ceci:
@Depends("unitPrice") // 1
@Max(9999999999L) // 2
public BigDecimal getUnitPriceInPesetas() {
 if (unitPrice == null) return null;
 return unitPrice.multiply(new BigDecimal("166.386")).setScale(0, BigDecimal.ROUND_HALF_UP);
}
Selon la définition ci-dessus, vous pouvez utiliser le code ainsi :
Product product = ...
product.setUnitPrice(2);
BigDecimal result = product.getUnitPriceInPesetas();
Et result contiendra 332.772.
Lorsque la propriété unitPriceInPesetas est affichée pour l'utilisateur, elle ne sera pas modifiable et son éditeur aura une taille de 10, indiquée par l'annotation @Max(9999999999L) (2). En outre, puisque @Depends("unitPrice") est spécifié (1), lorsque l'utilisateur changera la valeur de la propriété unitPrice dans l'interface utilisateur, la propriété unitPriceInPesetas sera recalculée et sa nouvelle valeur rafraîchie pour l'utilisateur.
A partir d'une propriété calculée, vous pouvez avoir accès directement aux connexions JDBC. En voici un exemple :
@Max(999)
public int getDetailsCount() {
 // An example of using JDBC
 Connection con = null;
 try {
 con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection(); // 1
 String table = MetaModel.get("InvoiceDetail").getMapping().getTable();
 PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
 " where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
 ps.setInt(1, getYear());
 ps.setInt(2, getNumber());
 ResultSet rs = ps.executeQuery();
 rs.next();
 Integer result = new Integer(rs.getInt(1));
 ps.close();
 return result;
 }
 catch (Exception ex) {
 log.error("Problem calculating details count of an Invoice", ex);
 // You can throw any runtime exception here
 throw new SystemException(ex);
 }
 finally {
 try {
 con.close();
 }
 catch (Exception ex) {
 }
 }
}
Oui, le code JDBC n'est pas très propre et joli, mais parfois, il peut aider dans des problèmes de performance. La classe DataSourceConnectionProvider vous permet la connexion associée à la même source de donnée que celle indiquée dans votre entité (Invoice dans notre cas). Cette classe peut être utilisée à votre convenance, mais vous pouvez accéder à une connexion JDBC en utilisant JNDI ou une autre méthode de votre choix. En fait, dans une propriété calculée, vous pouvez écrire n'importe quelle code que Java vous permet.
Si vous utilisez l'accès sur la base des propriétés, c'est-à-dire que vous annotez les getters et setters de votre classe, vous devez ajouter l'annotation @Transient à votre propriété calculée, comme ceci :
private long number;
 
@Id @Column(length=10) // Vous annotez le getter,
public long getNumber() { // afin que JPA utilise un accès à base de propriété pour votre classe
 return number;
}
public void setNumber(long number) {
 this.number = number;
}
 
@Transient // Vous devez annotez votre propriété calculée avec @Transient
public String getZoneOne() { // parce que vous utilisez l'accès à base de propriété
 return "In ZONE 1";
}

Formula (depuis v3.1.4)

En utilisant @Formulades annotations Hibernate, vous pouvez définir un calcul de votre propriété. Ce calcul est exprimé avec le langage SQL et est exécuté par la base de données, et non par Java. Vous n'avez besoin que d'écrire un fragment SQL valide comme ici:
@org.hibernate.annotations.Formula("UNITPRICE * 1.16")
private BigDecimal unitPriceWithTax;
public BigDecimal getUnitPriceWithTax() {
 return unitPriceWithTax;
}
L'utilisation est simple. Ecrivez votre calcule exactement de la même manière que vous le feriez dans un ordre SQL.
En général, les propriétés anotées avec @Formula sont en lecture seule, c'est-à-dire qu'elles bénéficient d'un getter et pas de setter. Lorsque l'objet est lu de la base de données, le calcul est executé par la base de données et la propriété prend la valeur retournée.
Cette option est une alternative au propriétés calculées. Elle a l'avantage que l'utilisateur peut la filtrer dans le mode liste et le désavantage que vous devez utiliser SQL au lieu de Java et vous ne pouvez pas utiliser @Depends pour le calcul dynamiques de la valeur

Calculateur de la valeur par défaut

Avec l'annotation @DefaultValueCalculator vous pouvez associer de la logique à votre propriété, dans le cas où la propriété est en lecture/écriture. Ce calculateur est destiné à calculer la valeur initiale d'une propriété. Par exemple :
@DefaultValueCalculator(CurrentYearCalculator.class)
private int year;
Dans ce cas, lorsque l'utilisateur essaye de créer une nouvelle facture (par exemple), il constatera que la valeur du champ année (year) est déjà définie, et qu'il peut la changer si besoin. La logique pour générer cette valeur initiale se trouve dans la classe CurrentYearCalculator que voici :
package org.openxava.calculators;
 
import java.util.*;
 
/**
 * @author Javier Paniza
 */
public class CurrentYearCalculator implements ICalculator {
 
 public Object calculate() throws Exception {
 Calendar cal = Calendar.getInstance();
 cal.setTime(new java.util.Date());
 return new Integer(cal.get(Calendar.YEAR));
 }
 
}
Il est possible de personnaliser le comportement d'un calculateur en spécifiant les valeurs de ses propriétés, comme ici :
@DefaultValueCalculator(
 value=org.openxava.calculators.StringCalculator.class,
 properties={ @PropertyValue(name="string", value="BONNE") }
)
private String relationWithSeller;
Dans cet exemple, pour calculer la valeur initiale, OpenXava crée une instance de StringCalculator et injecte la valeur BONNE dans la propriété string de StringCalculator avant d'appeler la méthode execute() qui initialise la valeur par défaut de la propriété relationwithSeller (relation avec le vendeur). Comme vous le voyez, l'utilisation de l'annotation @PropertyValue vous permet de créer des calculateurs réutilisables. @PropertyValue vous permet d'injecter des valeurs issues d'autres propriétés de cette façon :
@DefaultValueCalculator(
 value=org.openxava.test.calculators.CarrierRemarksCalculator.class,
 properties={
 @PropertyValue(name="drivingLicenceType", from="drivingLicence.type")
 }
)
private String remarks;
Dans ce cas, avant de lancer l'exécution du calculateur, OpenXava remplit la propriété drivingLicenceType (type de permis de conduire) de CarrierRemarksCalculator (calculateur de remarques sur le transporteur) avec la valeur affichée de la propriété type de la référence drivingLicence (permis de conduire). Comme vous le constatez, l'attribut from permet l'utilisation de propriété qualifiées (référence.propriété).
Vous pouvez aussi utilisez @PropertyValue sans from ni value :
@DefaultValueCalculator(value=DefaultProductPriceCalculator.class, properties=
 @PropertyValue(name="familyNumber")
)
Ci-dessus, OpenXava prend la valeur de la propriété affichée familyNumber (numéro de famille) et l'injecte dans la propriété familyNumber du calculateur. Ceci signifie que @PropertyValue(name="familyNumber) est équivalent à @PropertyValue(name="familyNumber" from="familyNumber"). Depuis un calculateur, vous avez un accès direct aux connexions JDBC. En voici un exemple :
@DefaultValueCalculator(value=DetailsCountCalculator.class,
 properties= {
 @PropertyValue(name="year"),
 @PropertyValue(name="number"),
 }
)
private int detailsCount;
 
Et la classe du calculateur :
package org.openxava.test.calculators;
 
import java.sql.*;
 
import org.openxava.calculators.*;
import org.openxava.util.*;
 
/**
 * @author Javier Paniza
 */
public class DetailsCountCalculator implements IJDBCCalculator { // 1
 
 private IConnectionProvider provider;
 private int year;
 private int number;
 
 public void setConnectionProvider(IConnectionProvider provider) { // 2
 this.provider = provider;
 }
 
 public Object calculate() throws Exception {
 Connection con = provider.getConnection();
 try {
 PreparedStatement ps = con.prepareStatement(
 "select count(*) from XAVATEST.INVOICEDETAIL “ +
 “where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
 ps.setInt(1, getYear());
 ps.setInt(2, getNumber());
 ResultSet rs = ps.executeQuery();
 rs.next();
 Integer result = new Integer(rs.getInt(1));
 ps.close();
 return result;
 }
 finally {
 con.close();
 }
 }
 
 public int getYear() {
 return year;
 }
 
 public int getNumber() {
 return number;
 }
 
 public void setYear(int year) {
 this.year = year;
 }
 
 public void setNumber(int number) {
 this.number = number;
 }
 
}
Afin de pouvoir utiliser JDBC, votre calculateur doit implémenter l'interface IJDBCCalculator (1) afin qu'il puisse recevoir une instance de IConnectionProvider (2) utilisable dans la méthode calculate().
OpenXava est livré avec une série de calculateurs prédéfinis dans le paquet org.openxava.calculators.

Valeurs par défaut à la création

Vous pouvez indiquer qu'une valeur doit être calculée juste avant la création d'une entité (enregistrement dans la base de données) pour la première fois. En général, pour les clés, il est conseillé d'utiliser JPA. Par exemple, si vous voulez utiliser une colonne d'identité (auto-incrémentée) comme clé :
@Id @Hidden
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
Vous pouvez utiliser d'autres méthodes de génération. Par exemple, une séquence de base de données peut être définie avec la méthode standard de JPA :
@SequenceGenerator(name="SIZE_SEQ", sequenceName="SIZE_ID_SEQ", allocationSize=1 )
@Hidden @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SIZE_SEQ")
private Integer id;
Si vous souhaitez générer une chaîne de caractères (de type java.lang.String) d'une longueur de 32, vous disposez des extensions Hibernate de JPA :
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String oid;
Consultez la section 9.1.9 de la spécification JPA 1.0 (qui fait partie de la recommandation JSR-220) pour apprendre plus sur @GeneratedValue. Si vous souhaitez utiliser votre propre logique pour générer une valeur à la création, ou que vous souhaitez une valeur générée pour une propriété ne faisant pas partie de la clé, vous ne pouvez pas utiliser l'annotation @GeneratedValue. Il est toutefois simple de régler ces cas avec JPA. Il faut simplement ajouter ce code à votre à votre classe :
@PrePersist
private void calculateCounter() {
 counter = new Long(System.currentTimeMillis()).intValue();
}
L'annotation JPA @PrePersist fait en sorte que cette méthode est exécutée avant d'insérer la première fois l'instance dans la base de données. Dans cette méthode, vous pouvez calculer avec votre propre logique les valeurs de vos propriétés qu'elles fassent ou non partie de la clé.

Validateur de propriété

L'annotation @PropertyValidator exécute la logique de validation de la valeur assignée à la propriété juste avant son enregistrement. Une propriété peut avoir plusieurs validateurs :
@PropertyValidators ({
 @PropertyValidator(value=ExcludeStringValidator.class, properties=
 @PropertyValue(name="string", value="MOTO")
 ),
 @PropertyValidator(value=ExcludeStringValidator.class, properties=
 @PropertyValue(name="string", value="COCHE"),
 onlyOnCreate=true
 )
})
private String description;
La technique pour configurer un validateur (avec @PropertyValue, though from attribute does not work, you must use value always) est exactement la même que pour les calculateurs. Avec l'attribut onlyOnCreate=true vous pouvez spécifier si le validateur ne s'exécute qu'à la création de l'objet et pas lorsque celui-ci est modifié.
Le code du validateur est le suivant :
package org.openxava.test.validators;
 
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
 
public class ExcludeStringValidator implements IPropertyValidator { // A
 
 private String string;
 
 public void validate(
 Messages errors, // 1
 Object value, // 2
 String objectName, // 3
 String propertyName) // 4
 throws Exception {
 if (value==null) return;
 if (value.toString().indexOf(getString()) >= 0) {
 errors.add("exclude_string", propertyName, objectName, getString());
 }
 }
 
 public String getString() {
 return string==null?"":string;
 }
 
 public void setString(String string) {
 this.string = string;
 }
 
}
Un validateur doit implémenter l'interface IPropertyValidator (A) ce qui oblige votre validateur à posséder une méthode validate() dans laquelle la validation de la propriété est exécutée. Les arguments de la méthode validate() sont les suivants :
  1. Messages errors. Un objet de type Messages qui représente une suite de messages (comme une collection intelligente) à laquelle vous pouvez ajouter vos erreurs de validation
  2. Object value. La valeur à valider
  3. String objectName. Le nom de l'objet contenant la propriété à valider. Utile pour les messages d'erreur
  4. String propertyName. Le nom de la propriété à valider. Utile pour les messages d'erreur
Comme vous le voyez, si votre code rencontre une erreur de validation, vous devez la signaler en ajoutant un identificateur de message et les arguments (avec errors.add()). Si vous souhaitez avoir un message significatif, vous pouvez ajouter dans vos fichier i18n la définition suivante :
exclude_string={0} ne peut contenir {2} dans {1}
Si l'identificateur de message ajouté n'est pas trouvé dans les fichiers de ressource, l'identificateur est affiché tel quel. Toutefois, il est recommandé de toujours utiliser les fichier de ressources i18n.
La validation est considérée comme réussie si aucun message n'a été ajouté et ratée si un ou plusieurs messages ont été ajoutés. OpenXava rassemble tous les messages de tous les validateurs avant l'enregistrement et s'il y a des messages, les affiche après avoir annulé l'enregistrement.
Please, translate to French: Since v4.6.1 is also possible to use in the validator the message of @PropertyValidator. That is, you can write:
@PropertyValidator(value=BookTitleValidator.class, message="{rpg_book_not_allowed}")
private String title;
Please, translate to French: If the message is between braces is get from i18n files, if not is used as is.
Moreover, you have to implement the IWithMessage interface in your validator:
public class BookTitleValidator implements IPropertyValidator, IWithMessage {
 
    private String message;
 
    public void setMessage(String message) throws Exception {
        this.message = message; // This message is from @PropertyValidator
    }
 
    public void validate(Messages errors, Object value, String propertyName, String modelName) {
       if (((String)value).contains("RPG")) {
           errors.add(message); // You can add it directly
       }
    }
 
}
Please, translate to French: The message specified in the @PropertyValidator annotation, rpg_book_not_allowed, is injected in the validator calling setMessage(). This message can be added directly as an error.
Le paquet org.openxava.validators contient des validateurs génériques. @PropertyValidator est défini comme une contrainte d'Hibernate Validator (depuis v3.0.1)
Please, translate to French: If you need to use JPA in your validator, please see Using JPA from a Validator or Callback.

Validateur par défaut (depuis v2.0.3)

Vous pouvez définir un validateur de propriété par défaut dépendant de son type ou stéréotype. Pour ce faire, vous avez besoin d'utiliser le fichier xava/validators.xml de votre projet pour y définir les validateurs par défaut.
Par exemple, vous pouvez définir le fichier xava/validators.xml ainsi :
<validators>
 <default-validator>
 <validator-class
 class="org.openxava.test.validators.PersonNameValidator"/>
 <for-stereotype stereotype="PERSONNE_NOM"/>
 </default-validator>
</validators>
Ainsi, vous associez le validateur PersonNameValidator au stéréotype PERSONNE_NOM. A présent, vous pouvez définir une propriété d'entité comme la suivante :
@Required @Stereotype("PERSONNE_NOM")
private String name;
Cette propriété sera validée en utilisant le validateur PersonNameValidator même si la propriété elle-même ne définit pas de validateur. PersonNameValidator est appliqué à toutes les propriétés qui portent le stéréotype PERSONNE_NOM. Il est également possible d'associer un validateur à un type.
Dans validators.xml, vous avez aussi le loisir de déclarer des validateurs déterminant si une valeur requise est présente (exécutés lorsque l'annotation @Required est présente). En outre, vous pouvez définir des noms (alias) pour les classes de validateurs.
Pour en savoir plus sur les validateurs, examinez les fichiers OpenXava/xava/default-validators.xml et OpenXavaTest/xava/validators.xml. Les validateurs par défaut ne sont pas pris en compte lorsque vous utilisez l'API JPA pour enregistrer vos objets.

Références

Une référence vous permet d'accéder à une entité depuis une autre entité. Une référence est traduite dans le code Java comme une propriété (avec son getter et son setter) dont le type est le modèle Java référencé. Par exemple, un Client peut contenir une référence vers son Vendeur, ce qui nous permet d'écrire le code pour accéder au nom du Vendeur de ce Client ainsi :
Client client = ...
client.getVendeur().getNom();
La syntaxe d'une référence est définie comme suit:
@Required // 1
@Id // 2
@SearchKey // 3 depuis v3.0.2
@DefaultValueCalculator // 4
@ManyToOne( // 5
 optional=false // 1
)
private type nomReference; // 5
public type getNomReference() { ... } // 5
public void setNomReference(type nouvelleValeur) { ... } // 5
  1. @ManyToOne(optional=false) (JPA), @Required (OX) (optionnel, celui de JPA est conseillé). Indique si la référence est requise ou non. Lors de l'enregistrement, OpenXava vérifie que les références requise sont présentes, sinon il annule l'opération et une liste d'erreurs est retournée.
  2. @Id (JPA, optionnel). Indique si la référence fait partie de la clé. La combinaison de propriétés et de références clés doivent correspondre à un ensemble de colonnes de la base de données avec des valeurs uniques, typiquement des clés primaires.
  3. @SearchKey (OX, un seul, optionnel) (depuis v3.0.2). Les références clés de recherche sont employées par l'utilisateur comme clé pour chercher des objets. Elles sont éditable dans l'interface utilisateur de la référence permettant à l'utilisateur d'entrer une valeur de recherche. OpenXava utilise les membres @Id par défaut pour la recherche et si ceux-ci sont cachés, il utilise la première propriété visible dans la vue. Avec @SearchKey, vous pouvez explicitement choisir des référence pour la recherche.
  4. @DefaultValueCalculator (OX, un seul, optionnel). Implémente la logique pour calculer la valeur initiale de la référence. Le calculateur doit retourner la valeur de la clé qui peut être une valeur simple (seulement si la clé de l'objet référencé est simple) ou une objet clé (un objet spécial qui enrobe une combinaison de clés).
  5. Déclaration de référence. Une référence traditionnelle Java avec ses getter et setter. La référence est marquée avec //@ManyToOne// (JPA) et le type doit être une autre entité.
Voici un petit exemple de références :
@ManyToOne
private Vendeur vendeur; // 1
public vendeur getVendeur() {
 return vendeur;
}
public void setVendeur(Vendeur vendeur) {
 this.vendeur = vendeur;
}
 
@ManyToOne(fetch=FetchType.LAZY)
private Vendeur vendeurAlternatif; // 2
public Vendeur getVendeurAlternatif() {
 return vendeurAlternatif;
}
public void setVendeurAlternatif(Vendeur vendeurAlternatif) {
 this.vendeurAlternatif = vendeurAlternatif;
}
  1. Une référence nommée vendeur vers une entité Vendeur
  2. Une référence nommée vendeurAlternatif vers une entité Vendeur. Dans ce cas, nous utilisons fetch=FetchType.LAZY pour que la donnée soit lue à partir de la base de donnée uniquement à la demande. Ceci est une approche plus efficace, mais n'est pas le standard JPA, c'est pourquoi il est conseillé de toujours utiliser fetch=FetchType.LAZY dans la déclaration de références.
Si nous considérons que le code ci-dessus fait partie d'une entité Client, nous pourrions écrire :
Client client = ...
Vendeur vendeur = client.getVendeur();
Vendeur vendeurAlternatif = client.getVendeurAlternatif();

Calculateur de la valeur par défaut dans les références

Dans le cadre d'une référence, @DefaultValueCalculator travaille de la même manière que pour une propriété. La seule différence, c'est que le calculateur doit retourner la clé de la référence. Par exemple, dans le cas d'une référence avec clé simple, vous pouvez écrire :
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
 @PropertyValue(name="value", value="2")
)
private Family family;
La méthode calculate() de la classe IntegerCalculator est définie ainsi :
public Object calculate() throws Exception {
 return new Integer(value);
}
 
Comme vous le voyez, un objet java.lang.Integer est retourné. Cela signifie que la valeur par défaut pour family vaut 2.
Dans le cas d'une clé composée :
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
 @JoinColumn(name="ZONE", referencedColumnName="ZONE"),
 @JoinColumn(name="WAREHOUSE", referencedColumnName="NUMBER")
})
@DefaultValueCalculator(DefaultWarehouseCalculator.class)
private Warehouse warehouse;
Le code du calculateur retourne un objet de type Warehouse:
package org.openxava.test.calculators;
 
import org.openxava.calculators.*;
 
/**
 * @author Javier Paniza
 */
public class DefaultWarehouseCalculator implements ICalculator {
 
  public Object calculate() throws Exception {
    Warehouse key = new Warehouse();
    key.setNumber(4);
    key.setZoneNumber(4);
    return key;
  }
 
}

Utilisation de références comme clés

Vous pouvez utiliser les référence comme clés, ou partie de la clé, de votre entité. Vous devez annoter votre référence avec @Id et utiliser une classe d'identification de la clé comme ici :
@Entity
@IdClass(AdditionalDetailKey.class)
public class AdditionalDetail {
 
 // JoinColumn is also specified in AditionalDetailKey because
 // a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
 @Id @ManyToOne(fetch=FetchType.LAZY)
 @JoinColumn(name="SERVICE")
 private Service service;
 
 @Id @Hidden
 private int counter;
 
 ...
 
}
Et la classe d'identification de la clé :
public class AdditionalDetailKey implements java.io.Serializable {
 
 @ManyToOne(fetch=FetchType.LAZY)
 @JoinColumn(name="SERVICE")
 private Service service;
 
 @Hidden
 private int counter;
 
 // equals, hashCode, toString, getters and setters
 ...
 
 }
Vous devez écrire la classe d'identification de clé même si la clé était uniquement la référence avec seulement une colonne de jointure. Il est conseillé de n'utiliser cette méthode qu'en travaillant avec des bases de données existantes. Si vous travaillez avec votre propre schéma de base de données, utilisez plutôt les identifiants auto-générés.

Références embarquées

Voici un exemple d'une propriété Address embarquée (avec l'annotation @Embedded) référencée par l'entité principale. Dans l'entité principale vous écrivez :
@Embedded
private Address address;
Et vous devez définir la classe Address comme embarquable :
package org.openxava.test.model;
 
import javax.persistence.*;
import org.openxava.annotations.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Embeddable
public class Address implements IWithCity { // 1
 
 @Required @Column(length=30)
 private String street;
 
 @Required @Column(length=5)
 private int zipCode;
 
 @Required @Column(length=20)
 private String city;
 
 // ManyToOne dans un Embeddable n'est pas supporté par JPA 1.0 (voir 9.1.34),
 // mais l'implémentation d'Hibernate le supporte.
 @ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")
 private State state; // 2
 
 public String getCity() {
 return city;
 }
 
 public void setCity(String city) {
 this.city = city;
 }
 
 public String getStreet() {
 return street;
 }
 
 public void setStreet(String street) {
 this.street = street;
 }
 
 public int getZipCode() {
 return zipCode;
 }
 
 public void setZipCode(int zipCode) {
 this.zipCode = zipCode;
 }
 
 public State getState() {
 return state;
 }
 
 public void setState(State state) {
 this.state = state;
 }
 
}
Comme montré ci-dessus, une classe embarquable peut implémenter une interface (1) et contenir des références (2) entre autres choses. Toutefois, il ne peut utiliser les méthodes de rappel JPA.
Le code ci-dessus peut être utilisé ainsi, pour la lecture des objets :
Customer customer = ...
Address address = customer.getAddress();
address.getStreet(); // pour obtenir la valeur
Ou de cette façon pour spécifier une adresse (définie par l'objet Address) :
// éfinition d'une nouvelle adresse
Address address = new Address();
address.setStreet(“Ma rue”);
address.setZipCode(1007);
address.setCity(“Lausanne”);
address.setState(state);
customer.setAddress(address);
Dans cet exemple, vous avez une référence simple (pas de collection) et le code généré correspond à un JavaBean dont le cycle de vie est associé à l'entité qui le possède, c'est-à-dire que l'instance Address est supprimée et créée au travers de la classe Customer. Une instance d'Address n'aura jamais une vie propre et ne peut pas être partagée avec d'autres instances de Customer.

Collections

Collections de entités

Il est possible de définir une collection de références à des entités. Une collection est une propriété Java de type java.util.Collection. En voici la syntaxe :
@Size // 1
@Condition // 2
@OrderBy // 3
@XOrderBy // 4
@OneToMany/@ManyToMany // 5
private Collection<YourEntity> collectionName; // 5
public Collection<YourEntity> getCollectionName() { ... } // 5
public void setCollectionName(Collection<YourEntity> newValue) { ... } // 5
  1. @Size (BV, HV, optionnel). Nombre minimal (min) ou maximum (max) d'éléments attendus. Ceci est validé juste avant l'enregistrement.
  2. @Condition (OX, optionnel). Restreint les éléments qui font partie de la collection.
  3. @OrderBy (JPA, optionnel). Les éléments de la collection seront définis selon cet ordre.
  4. @XOrderBy (OX, optionnel). @OrderBy de JPA ne permet pas d'utiliser des propriétés qualifiées (propriétés de références). @XOrderBy compense ce manque.
  5. Déclaration de collection. Une déclaration Java ordinaire d'une collection avec ses getter et setter. La collection est marquée avec @ManyToOne (JPA) ou @ManyToMany (JPA) et le type doit être une autre entité.
Voyons quelques exemples :
@OneToMany (mappedBy="invoice")
private Collection<Delivery> deliveries;
public Collection<Delivery> getDeliveries() {
 return deliveries;
}
public void setDeliveries(Collection<Delivery> deliveries) {
 this.deliveries = deliveries;
}
Si ce code est présent dans une classe Invoice (facture), vous définissez donc une collection de deliveries (livraisons) associée à cette facture. Les détails qui définissent cette relation sont expliqués dans le chapitre dédié au mapping objet-relationnel. Vous utilisez mappedBy="invoice" pour indiquer que la référence invoice de la classe Delivery est utilisée pour lier cette collection.
A présent, vous pouvez écrire le code suivant pour faire quelque-chose avec les livraisons (deliveries) associée à cette facture :
Invoice invoice = ...
for (Delivery delivery: invoice.getDeliveries()) {
 delivery.doSomething();
}
Voyons à présent une exemple un peu plus complexe toujours dans la classe Invoice :
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE) // 1
@OrderBy("serviceType desc") // 2
@org.hibernate.validator.Size(min=1) // 3
private Collection<InvoiceDetail> details;
  1. Utiliser REMOVE comme type de cascade a pour effet de supprimer tous les détails associés lorsque l'utilisateur supprimer l'objet Invoice.
  2. Avec @OrderBy, vous forcez l'ordre des détails dans la collection selon la propriété serviceType (type de service) du détail.
  3. La restriction @Size(min=1) force le fait qu'un objet Invoice doit contenir au moins un détail pour être valide.
Vous avez toute la liberté de définir comment les données de la collection sont obtenues. Avec @Condition, vous pouvez écraser les conditions par défaut :
@Condition(
 "${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
 "${warehouse.number} = ${this.warehouse.number} AND " +
 "NOT (${number} = ${this.number})"
)
public Collection<Carrier> getFellowCarriers() {
 return null;
}
Si vous avez cette collection dans un objet Carrier (transporteur), vous pouvez obtenir tous les transporteurs liés au même entrepôt (Warehouse) sauf lui-même, soit tous les transporteurs concurrents. Comme vous pouvez le constater, vous pouvez utiliser le mot-clé this dans la condition afin de référencer la valeur de la propriété de l'objet courant. @Condition est uniquement appliqué dans le cadre de l'interface graphique générée automatiquement par OpenXava. Si vous faites appel directement à la méthode getFellowCarriers(), elle retournera toujours null.
Si cela ne vous suffit pas, vous pouvez écrire la logique qui retourne la collection. L'exemple précédant peut aussi être écrit de la manière suivante :
public Collection<Carrier> getFellowCarriers() {
 Query query = XPersistence.getManager().createQuery("from Carrier c where " +
 "c.warehouse.zoneNumber = :zone AND " +
 "c.warehouse.number = :warehouseNumber AND " +
 "NOT (c.number = :number) ");
 query.setParameter("zone", getWarehouse().getZoneNumber());
 query.setParameter("warehouseNumber", getWarehouse().getNumber());
 query.setParameter("number", getNumber());
 return query.getResultList();
}
Comme vous le voyez, c'est une méthode getter traditionnelle. Trivialement, c'est une méthode qui retourne un objet de type java.util.Collection dont les éléments sont de type Carrier.
Les références dans les collections sont bidirectionnelles. Ceci signifie que si un Vendeur possède des clients, le Client doit avoir une référence vers le Vendeur. Mais il est possible que dans Client, il existe plusieurs références à Vendeur (par exemple vendeur et vendeurAlternatif). JPA ne sachant pas lequel choisir, il faut donc utiliser l'attribut mappedBy de @OneToMany. Voici une manière de l'utiliser pour indiquer que c'est vendeur et non vendeurAlternatif qui est référencé dans cette collection :
@OneToMany(mappedBy="vendeur")
private Collection<Client> clients;
L'annotation @ManyToMany (JPA) permet de définir une collection avec une multiplicité de plusieurs à plusieurs, comme ici :
@Entity
public class Client {
 ...
 @ManyToMany
 private Collection<Etat> etats;
 ...
}
 
Dans ce cas, un client possède plusieurs états, et un état peut être utilisé par plusieurs clients.

Collections embarquées

Les collections embarquées ne sont pas prises en charge par JPA 1.0, mais vous pouvez les simuler en utilisant des collections d'entités avec les type de cascade REMOVE ou ALL. OpenXava gère ces collections d'une manière spéciale, comme si c'était des collections embarquées. A présent, voyons un exemple de collections embarquées en commençant par l'entité principale :
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection details;
Notez que CascadeType.REMOVE est utilisé et que InvoiceDetail est une entité et pas une classe embarquable :
package org.openxava.test.model;
 
import java.math.*;
 
import javax.persistence.*;
 
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.*;
import org.openxava.calculators.*;
import org.openxava.test.validators.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@EntityValidator(value=InvoiceDetailValidator.class,
 properties= {
 @PropertyValue(name="invoice"),
 @PropertyValue(name="oid"),
 @PropertyValue(name="product"),
 @PropertyValue(name="unitPrice")
 }
)
public class InvoiceDetail {
 
 @ManyToOne // La récupération paresseuse ne fonctionne pas lors de la suppression d'un détail de la facture (Invoice)
 private Invoice invoice;
 
 @Id @GeneratedValue(generator="system-uuid") @Hidden
 @GenericGenerator(name="system-uuid", strategy = "uuid")
 private String oid;
 
 private ServiceType serviceType;
 public enum ServiceType { SPECIAL, URGENT }
 
 @Column(length=4) @Required
 private int quantity;
 
 @Stereotype("MONEY") @Required
 private BigDecimal unitPrice;
 
 @ManyToOne(fetch=FetchType.LAZY, optional=false)
 private Product product;
 
 @DefaultValueCalculator(CurrentDateCalculator.class)
 private java.util.Date deliveryDate;
 
 @ManyToOne(fetch=FetchType.LAZY)
 private Seller soldBy;
 
 @Stereotype("MEMO")
 private String remarks;
 
 @Stereotype("MONEY") @Depends("unitPrice, quantity")
 public BigDecimal getAmount() {
 return getUnitPrice().multiply(new BigDecimal(getQuantity()));
 }
 
 public boolean isFree() {
 return getAmount().compareTo(new BigDecimal("0")) <= 0;
 }
 
 @PostRemove
 private void postRemove() {
 invoice.setComment(invoice.getComment() + "DETAIL DELETED");
 }
 
 public String getOid() {
 return oid;
 }
 public void setOid(String oid) {
 this.oid = oid;
 }
 public ServiceType getServiceType() {
 return serviceType;
 }
 public void setServiceType(ServiceType serviceType) {
 this.serviceType = serviceType;
 }
 public int getQuantity() {
 return quantity;
 }
 public void setQuantity(int quantity) {
 this.quantity = quantity;
 }
 public BigDecimal getUnitPrice() {
 return unitPrice==null?BigDecimal.ZERO:unitPrice;
 }
 public void setUnitPrice(BigDecimal unitPrice) {
 this.unitPrice = unitPrice;
 }
 
 public Product getProduct() {
 return product;
 }
 
 public void setProduct(Product product) {
 this.product = product;
 }
 
 public java.util.Date getDeliveryDate() {
 return deliveryDate;
 }
 
 public void setDeliveryDate(java.util.Date deliveryDate) {
 this.deliveryDate = deliveryDate;
 }
 
 public Seller getSoldBy() {
 return soldBy;
 }
 
 public void setSoldBy(Seller soldBy) {
 this.soldBy = soldBy;
 }
 
 public String getRemarks() {
 return remarks;
 }
 
 public void setRemarks(String remarks) {
 this.remarks = remarks;
 }
 
 public Invoice getInvoice() {
 return invoice;
 }
 
 public void setInvoice(Invoice invoice) {
 this.invoice = invoice;
 }
 
}
Ceci est une entité complexe, avec des validateurs, des calculateurs, des références, etc. Il a fallu également définir une référence à l'entité propriétaire (invoice). Dans le cas ci-dessus, lorsqu'une facture (instance d'Invoice) est supprimée, tous ses détails le sont aussi. En outre, il existe des différences au niveau de l'interface graphique dont vous pouvez apprendre plus en consultant le chapitre Vue.

Element collections (new in v5.0)

Please, translate this section to French
Since JPA 2.0 you can define a collection of real embeddable objects. We call these collections element collections.
This is the syntax for element collections:
@Size // 1
@OrderBy // 2
@ElementCollection // 3
private Collection<YourEmbeddableClass> collectionName; // 3
public Collection<YourEmbeddableClass> getCollectionName() { ... } // 3
public void setCollectionName(Collection<YourEmbeddableClass> newValue) { ... } // 3
 
  1. @Size (BV, HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
  2. @OrderBy (JPA, optional): The elements in collections will be in the indicated order.
  3. Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @ElementCollection (JPA). The elements must be embeddable classes.
The elements in the collection are saved all at once at the same time of the main entity. Moreover, the generated user interface allows the user to modify all the elements of the collection at the same time.
An embeddable class that is contained within an element collection must not contain collections of any type.

Let's see an example. First you have to define the collection in the main entity:
@Entity
public class Quote extends Identifiable {
    ...
    @ElementCollection
    private Collection<QuoteDetail> details;
 
    public Collection<QuoteDetail> getDetails() {
        return details;
    }
    public void setDetails(Collection<QuoteDetail> details) {
        this.details = details;
    }
    ...
}
Then define your embeddable class:
@Embeddable
public class QuoteDetail {
 
    @ManyToOne(fetch=FetchType.LAZY, optional=false) // 1
    private Product product;
 
    @Required // 2
    private BigDecimal unitPrice;
 
    @Required
    private int quantity;
 
    private Date availabilityDate;
 
    @Column(length=30)
    private String remarks;
 
    @Column(precision=10, scale=2)
    @Depends("unitPrice, quantity")
    public BigDecimal getAmount() { // 3
        return getUnitPrice().multiply(new BigDecimal(getQuantity()));
    }
    ...
}
As you can see, an embeddable class used in an element collection can contain references(1), validations(2) and calculated properties(3) among other things.

Méthodes

Les méthodes sont définies dans une entité OpenXava (en réalité, une entité JPA) comme dans une classe Java classique. Par exemple :
public void increasePrice() {
 setUnitPrice(getUnitPrice().multiply(new BigDecimal("1.02")).setScale(2));
}
Les méthodes sont la sauce des objets. Sans elles, une entité ne serait qu'une bête structure de données. Lorsque c'est possible, il est toujours conseillé de placer la logique métier dans des méthodes (couche modèle) plutôt que dans des actions (couche contrôleur).

Requêtes

Les requêtes sont des méthodes Java statiques qui vous permettent de chercher un ou plusieurs objets qui répondent à des critères, comme dans ces exemples :
public static Customer findByNumber(int number) throws NoResultException {
 Query query = XPersistence.getManager().createQuery(
 "from Customer as o where o.number = :number");
 query.setParameter("number", number);
 return (Customer) query.getSingleResult();
}
 
public static Collection findAll() {
 Query query = XPersistence.getManager().createQuery("from Customer as o");
 return query.getResultList();
}
 
public static Collection findByNameLike(String name) {
 Query query = XPersistence.getManager().createQuery(
 "from Customer as o where o.name like :name order by o.name desc");
 query.setParameter("name", name);
 return query.getResultList();
}
Voici comment utiliser ces méthodes :
Customer customer = Customer.findByNumber(8);
Collection javieres = Customer.findByNameLike(%JAVI%);
L'utilisation des méthodes de recherche rendent le code beaucoup plus lisible qu'avec la syntaxe "bavarde" des requêtes JPA. Mais ceci n'est qu'une recommandation de style, car vous pouvez choisir d'utiliser les requêtes JPA plutôt que ces méthodes.

Validateur d'entité

Une annotation @EntityValidator permet de définir une validation au niveau de l'entité. Lorsque vous avez besoin d'exécuter une validation sur plusieurs propriétés en même temps, et que cette validation ne correspond logiquement à aucune de celle-là, vous pouvez utiliser ce type de validation dont la syntaxe est :
@EntityValidator(
 value=class, // 1
 onlyOnCreate=(true|false), // 2
 properties={ @PropertyValue ... } // 3
)
  1. value (obligatoire). La classe qui implémente la logique de validation. Elle doit implémenter l'interface IValidator.
  2. onlyOnCreate (optionnel). Si vrai (true), le validateur n'est exécuté qu'à la création de l'objet et non lorsqu'un objet existant est modifié. La valeur par défaut est faux (false)
  3. properties (plusieurs @PropertyValue, optionnel). Permet de déclarer les valeurs des propriétés du validateur avant de l'exécuter.
Un exemple :
@EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
 @PropertyValue(name="limit", value="100"),
 @PropertyValue(name="description"),
 @PropertyValue(name="unitPrice")
})
public class Product {
Et le code du validateur :
package org.openxava.test.validators;
 
import java.math.*;
 
/**
 * @author Javier Paniza
 */
 
public class CheapProductValidator implements IValidator { // 1
 
 private int limit;
 private BigDecimal unitPrice;
 private String description;
 
 public void validate(Messages errors) { // 2
 if (getDescription().indexOf("CHEAP") >= 0 ||
 getDescription().indexOf("BARATO") >= 0 ||
 getDescription().indexOf("BARATA") >= 0) {
 if (getLimiteBd().compareTo(getUnitPrice()) < 0) {
 errors.add("cheap_product", getLimitBd()); // 3
 }
 }
 }
 
 public BigDecimal getUnitPrice() {
 return unitPrice;
 }
 
 public void setUnitPrice(BigDecimal decimal) {
 unitPrice = decimal;
 }
 
 public String getDescription() {
 return description==null?"":description;
 }
 
 public void setDescription(String string) {
 description = string;
 }
 
 public int getLimit() {
 return limit;
 }
 
 public void setLimit(int i) {
 limit = i;
 }
 
 private BigDecimal getLimitBd() {
 return new BigDecimal(Integer.toString(limit));
 }
 
}
Un validateur doit implémenter l'interface IValidator (1) ce qui force l'écriture de la méthode validate(Messages messages). Dans cette méthode, les identifiants des messages d'erreurs (ceux déclarés dans les fichiers i18n) sont ajoutés. Si le processus entier de validation (soit l'exécution de tous les validateurs) produit au moins un message d'erreur, OpenXava interrompt l'opération d'enregistrement et informe l'utilisateur des erreurs.
Dans cet exemple, vous pouvez voir que les propriétés description et unitPrice sont utilisés dans la validation, c'est pourquoi le validateur est placé au niveau du modèle et non des propriétés individuelles, car le cadre de la validation dépasse une seule propriété.
Please, translate to French: Since v4.6.1 the validator can implement IWithMessage to inject the message from @EntityValidator, it works like in the property validator case.
Il est possible d'appliquer plusieurs validateurs d'entité à une entité, comme ceci :
@EntityValidators({
 @EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
 @PropertyValue(name="limit", value="100"),
 @PropertyValue(name="description"),
 @PropertyValue(name="unitPrice")
 }),
 @EntityValidator(value=org.openxava.test.validators.ExpensiveProductValidator.class, properties= {
 @PropertyValue(name="limit", value="1000"),
 @PropertyValue(name="description"),
 @PropertyValue(name="unitPrice")
 }),
 @EntityValidator(value=org.openxava.test.validators.ForbiddenPriceValidator.class,
 properties= {
 @PropertyValue(name="forbiddenPrice", value="555"),
 @PropertyValue(name="unitPrice")
 },
 onlyOnCreate=true
 )
})
public class Product {
@EntityValidator est défini comme une contrainte d'Hibernate Validator (depuis v3.0.1)
Please, translate to French: If you need to use JPA in your validator, please see Using JPA from a Validator or Callback.

Validateur de suppression

L'annotation @RemoveValidator est également un validateur au niveau du modèle, tout comme l'annotation@EntityValidator. Mais dans ce cas, la validation est exécutée juste avant la suppression d'un objet et elle a la possibilité d'annuler cette opération. La syntaxe est la suivante :
@RemoveValidator(
 value=class, // 1
 properties={ @PropertyValue ... } // 2
)
  1. value (requis). La classe qui implémente la logique de validation. Cette classe doit implémenter l'interface IRemoveValidator
  2. properties (plusieurs @PropertyValue, optionnel). Permet de déclarer les valeurs des propriétés du validateur avant de l'exécuter.
Un possible exemple :
@RemoveValidator(value=DeliveryTypeRemoveValidator.class,
 properties=@PropertyValue(name="number")
)
public class DeliveryType {
Le validateur est défini comme ceci :
package org.openxava.test.validators;
 
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
public class DeliveryTypeRemoveValidator implements IRemoveValidator { // 1
 
 private DeliveryType deliveryType;
 private int number; // We use this (instaed of obtaining it from deliveryType)
 // for testing @PropertyValue for simple properties
 
 public void setEntity(Object entity) throws Exception { // 2
 this.deliveryType = (DeliveryType) entity;
 }
 
 public void validate(Messages errors) throws Exception {
 if (!deliveryType.getDeliveries().isEmpty()) {
 errors.add("not_remove_delivery_type_if_in_deliveries", new Integer(getNumber())); // 3
 }
 }
 
 public int getNumber() {
 return number;
 }
 
 public void setNumber(int number) {
 this.number = number;
 }
 
}
Le validateur implémente l'interface IRemoveValidator (1) ce qui force la présence de la méthode setEntity() qui reçoit en paramètre l'objet à supprimer. Si une erreur de validation est ajoutée à l'objet Messages envoyé à la méthode validate(), la validation de suppression échoue. Si, après avoir exécuté toutes les validations, des erreurs de validation existent, OpenXava annule la suppression de l'objet et informe l'utilisateur avec la liste des erreurs.
Dans l'exemple, le validateur vérifie s'il y a des livraisons (deliveries) qui utilisent ce type de livraison (DeliveryType) avant de supprimer celui-ci.
Comme dans le cas de l'annotation @EntityValidator, il est possible de déclarer plusieurs annotations @RemoveValidator en utilisant l'annotation @RemoveValidators. Les validateurs de suppression sont exécutés lors de la suppressions des objets par OpenXava (depuis MapFacade ou avec les actions standards), mais pas en utilisant directement l'API JPA. Si vous souhaitez appliquer une restriction en utilisant l'API JPA, utilisez l'annotation @PreRemove devant votre méthode.

Méthodes de rappel JPA

Avec l'annotation @PrePersist, vous pouvez injecter votre propre logique exécutée juste avant le premier enregistrement de l'objet (que son état devienne persistant dans la base de donnée). Dans cet exemple, chaque fois qu'un type de livraison (DeliveryType) est créé, une description est ajoutée :
@PrePersist
private void prePersist() {
 setDescription(getDescription() + " créé");
}
Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la création de l'objet dans la base de données.
Avec l'annotation @PreUpdate, vous pouvez de la même manière injecter votre logique à exécuter avant l'enregistrement de la mise à jour de l'état de votre objet, c'est-à-dire juste avant l'exécution de l'ordre UPDATE dans la base de données. Dans cet exemple, chaque fois qu'un type de livraison est modifié, un suffixe est ajouté à la description :
@PreUpdate
private void preUpdate() {
 setDescription(getDescription() + " modifié");
}
Comme vous pouvez le constater, la méthode est identique aux autres méthodes, sauf qu'elle exécutée automatiquement juste avant la modification de l'objet dans la base de données.
Toutes les annotations JPA sont disponibles dans OpenXava : @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate et @PostLoad

Méthodes de rappel OX (à partir de la V4.0.1)

Please, translate this section to French
En utilisant @PreCreate vous pouvez définir une logique qui sera exécutée avant la persistance de l'objet. Vous pouvez ainsi effectuer des opérations de gestion des entités ou des requêtes qui ne sont pas autorisées dans les méthodes de rappel JPA.

Par exemple, si nous voulons créer un client et l'affecter à une facture au cas où aucun client n'aurait été spécifié :
@PreCreate
public void onPreCreate() {
 // Création automatique d'un nouveau client
 if (getCustomer() == null) {
 Customer cust = new Customer();
 cust.setName(getName());
 cust.setAddress(getAddress());
 cust = XPersistence.getManager().merge(cust);
 setCustomer(cust);
 }
}
L'opération de gestion de l'entité n'affectera pas le comportement de la méthode de rappel. Tous comme avec @PreCreate, les méthodes possédant les annotations @PostCreate et @PreDelete font partie de la même transaction, ce qui fait que l'utilisation de ces annotations conserve l'intégrité de l'information sans aucun effort supplémentaire de la part du développeur. Combinée aux annotations JPA, l'ordre d'exécution de chaque méthode de rappel est le suivant :
Pour la création d'une entité : @PreCreate, @PrePersist(JPA), @PostPersist(JPA) et @PostCreate.
Pour la suppression d'une entité : @PreDelete, @PreRemove(JPA), @PostRemove(JPA).
Les méthodes possédant ces annotations ne doivent retourner aucune valeur ni accepter aucun paramètre. Ces annotations ont été prévues pour les entités, et sont ignorées lorsqu'utilisées sur les listeners d'entité (entity listeners).

Héritage

OpenXava supporte les héritages Java et JPA. Par exemple, vous pouvez définir une classe annotée avec @MappedSuperclass ainsi :
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.hibernate.annotations.*;
import org.openxava.annotations.*;
 
/**
 * Classe de base pour les entités identifiées à l'aide d'un UUID. <p>
 *
 * @author Javier Paniza
 */
 
@MappedSuperclass
public class Identifiable {
 
 @Id @GeneratedValue(generator="system-uuid") @Hidden
 @GenericGenerator(name="system-uuid", strategy = "uuid")
 private String oid;
 
 public String getOid() {
 return oid;
 }
 
 public void setOid(String oid) {
 this.oid = oid;
 }
 
}
Vous pouvez définir une autre classe annotée @MappedSuperclass qui étend celle ci-dessus :
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
 
/**
 * Classe de base pour les entités ayant une propriété nom (name). <p>
 *
 * @author Javier Paniza
 */
@MappedSuperclass
public class Nameable extends Identifiable {
 
 @Column(length=50) @Required
 private String name;
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
}
A partir de maintenant, vous pouvez utiliser les classe Identifiable et Nameable pour définir vos entités, comme ici:
package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSON")
@AttributeOverrides(
 @AttributeOverride(name="name", column=@Column(name="PNAME"))
)
public class Human extends Nameable {
 
 @Enumerated(EnumType.STRING)
 private Sex sex;
 public enum Sex { MALE, FEMALE };
 
 public Sex getSex() {
 return sex;
 }
 public void setSex(Sex sex) {
 this.sex = sex;
 }
 
}
 
Il est également possible de faire un réel héritage d'une entité vers une autre entité :
package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@DiscriminatorValue("PRO")
public class Programmer extends Human {
 
 @Column(length=20)
 private String mainLanguage;
 
 public String getMainLanguage() {
 return mainLanguage;
 }
 
 public void setMainLanguage(String mainLanguage) {
 this.mainLanguage = mainLanguage;
 }
 
}
Vous pouvez créer un module OpenXava pour les classe Human et Programmer, mais pas Identifiable et Nameable directement. Dans le module Programmer, l'utilisateur n'a accès qu'aux instances de Programmer. Par contre, dans le module Human, l'utilisateur aura accès aux instances de Human. et de Programmer. En outre, si l'utilisateur essaye de voir le détail d'une instance de Programmer depuis le module Human, la vue Programmer sera affichée. Du vrai polymorphisme.
Please, translate to French: Since v4.5 OpenXava supports all inheritance features of JPA, including single table per class hierarchy, joined and table per class mapping strategies, before v4.5 only @AttributeOverrides and single table per class hierarchy mapping strategy was supported.

Clés composites

La méthode conseillée pour définir la clé d'une entité est d'utiliser un identifiant unique autgénéré (avec les annotations @Id et @GeneratedValue), mais parfois, par exemple en travaillant avec des base de données existantes, vous vous trouvez confronté à une entité mise en relation avec une table qui utilise plusieurs colonnes comme clé. Ce cas peut être résolu avec JPA (et donc OpenXava) de deux manières, soit avec l'annotation @IdClass ou avec l'annotation @EmbeddedId.

Classe d'identification

Si vous utilisez l'annotation @IdClass dans votre entité pour indiquer une classe clé , vous devez marquer les propriétés clés avec l'annotation @Id :
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
import org.openxava.jpa.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@IdClass(WarehouseKey.class)
public class Warehouse {
 
 @Id
 // La colonne esr également spécifiée dans la classe WarehouseKey à cause d'un bogue dans Hibernate, cf
 // http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
 @Column(length=3, name="ZONE")
 private int zoneNumber;
 
 @Id @Column(length=3)
 private int number;
 
 @Column(length=40) @Required
 private String name;
 
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public int getNumber() {
 return number;
 }
 
 public void setNumber(int number) {
 this.number = number;
 }
 
 public int getZoneNumber() {
 return zoneNumber;
 }
 
 public void setZoneNumber(int zoneNumber) {
 this.zoneNumber = zoneNumber;
 }
 
}
Vous devez également déclarer une classe d'identification, une classe régulière sérialisable contenant toutes les propriétés clés de votre entité :
package org.openxava.test.model;
 
import java.io.*;
 
import javax.persistence.*;
 
 
/**
 *
 * @author Javier Paniza
 */
 
public class WarehouseKey implements Serializable {
 
 @Column(name="ZONE")
 private int zoneNumber;
 private int number;
 
 @Override
 public boolean equals(Object obj) {
 if (obj == null) return false;
 return obj.toString().equals(this.toString());
 }
 
 @Override
 public int hashCode() {
 return toString().hashCode();
 }
 
 @Override
 public String toString() {
 return "WarehouseKey::" + zoneNumber+ ":" + number;
 }
 
 public int getNumber() {
 return number;
 }
 
 public void setNumber(int number) {
 this.number = number;
 }
 
 public int getZoneNumber() {
 return zoneNumber;
 }
 
 public void setZoneNumber(int zoneNumber) {
 this.zoneNumber = zoneNumber;
 }
 
}

Id embarqué

Dans cet exemple, une référence vers un objet embarquable (@Embeddable) est définie dans l'entité et annotée @EmbaddedId :
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
public class Warehouse {
 
 @EmbeddedId
 private WarehouseKey key;
 
 @Column(length=40) @Required
 private String name;
 
 public WarehouseKey getKey() {
 return key;
 }
 
 public void setKey(WarehouseKey key) {
 this.key = key;
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
}
Et votre clé est une classe embarquable contenant les propriétés clés de votre entité :

package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Embeddable
public class WarehouseKey implements java.io.Serializable {
 
 
 @Column(length=3, name="ZONE")
 private int zoneNumber;
 
 @Column(length=3)
 private int number;
 
 public int getNumber() {
 return number;
 }
 
 public void setNumber(int number) {
 this.number = number;
 }
 
 public int getZoneNumber() {
 return zoneNumber;
 }
 
 public void setZoneNumber(int zoneNumber) {
 this.zoneNumber = zoneNumber;
 }
 
}

Bean Validaton JSR-303 (new in v4.1)

Please, translate this section to French
OpenXava has full support for the standard Java for Bean Validation JSR-303. You can define your own constraints for your entities as explained in Bean Validation specification, and OpenXava will recognize them, showing the corresponding validation messages to the user. Consult the latest Hibernate Validator documentation to learn how to write a JSR-303 validator, since the current version of Hibernate Validator implements JSR-303.
Read the article in JavaLobby about using JSR-303 with OpenXava.

@AssertTrue

Since v4.9 OpenXava allows to inject properties and qualified properties (reference properties) that belong to validated bean, into the message identified by means of the message element of @AssertTrue. Example:
In this case we have an @AssertTrue annotating a field of the entity:
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.model.*
import javax.validation.constraints.*;
 
@Entity
public class Driver extends Identifiable{
 
 @Required
 @Column(length=40)
 private String name;
 
 @AssertTrue(message="{disapproved_driving_test}")
 private boolean approvedDrivingTest;
 
 @OneToMany(mappedBy="driver")
 private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
 
 //getters and setters...
}
{disapproved_driving_test} is the message identifier that is declared under i18n file like this:
disapproved_driving_test=Driver {name} can not be registered: must approve the driving test
If we try to create an entity with name=MIGUEL GRAU and approvedDrivingTest=false the next error message will be shown:

Driver MIGUEL GRAU can not be registered: must approved the driving test

In this case we have an @AssertTrue annotating a method of the entity:
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.model.*;
import javax.validation.constraints.*;
 
@Entity
public class Vehicle extends Identifiable{
 
 @Required
 @Column(length=15)
 private String type;
 
 @Required
 @Column(length=7)
 private String licensePlate;
 
 private boolean roadworthy;
 
 @ManyToOne
 private Driver driver;
 
 @AssertTrue(message="{not_roadworthy}")
 private boolean isRoadworthyToAssignTheDriver(){
     return driver == null || roadworthy;
 }
 
 //getters and setters...
}
{not_roadworthy} is the message identifier that is declared under i18n file like this:
not_roadworthy={type} plate {licensePlate} is not roadworthy. It can not be assigned to the driver {driver.name}
If we have an entity: type=AUTO, licensePlate=A1-0001 and roadworthy=false, and try to assign driver (name = MIGUEL GRAU), the validation method will fail and display the error message:

AUTO plate A1-0001 is not roadworthy. It can not be assigned to the driver MIGUEL GRAU

Validateur Hibernate (depuis v3.0.1)

OpenXava supporte complétement le validateur Hibernate . Vous pouvez définir vos propres contraintes pour vos entités comme expliqué dans la documentation du validateur Hibernate et OpenXava les reconnaîtra en montrant les messages de validations correspondant à l'utilisateur.
En outre, les annotations OpenXava @Required , @PropertyValidator et @EntityValidator sont définies comme des contraintes du validateur Hibernate, ce qui signifie que si vous enregitrez une objet directement avec JPA, ces contraintes s'appliqueront. D'un autre côté, les annotations @RemoveValidator, @PropertyValidator(onlyOnCreate=true), @EntityValidator(onlyOnCreate=true) et la validation par défaut d'OpenXava ne sont pas reconnues par le validateur Hibernate ou JPA, mais seulement par OpenXava.